package hk.polyu;


import java.awt.*;
import java.io.*;
import java.util.*;



import javax.swing.event.EventListenerList;


// Class use to read data
public class DataReceiver {

	// Singleton pattern
	private static DataReceiver inst = null;
	private DataReceiver()
	{
	}
	public static DataReceiver instance()
	{
		if (inst == null)
		{
			inst = new DataReceiver();
		}
		return inst;
	}
	// end singleton
	
	// event process
	EventListenerList l = new EventListenerList();
	
	public void addDataReceiveListener(DataReceiveListener listener)
	{
		l.add(DataReceiveListener.class, listener);
	}
	
	public void removeDataReceiveListener(DataReceiveListener listener)
	{
		l.remove(DataReceiveListener.class, listener);
	}
	
	protected void fireDataReceived(SensorData d) 
	{
		//FIXME: does there have any synchronization issue when fire event.
	     // Guaranteed to return a non-null array
		 DataReceiveListener[] listeners = l.getListeners(DataReceiveListener.class);
	     // Process the listeners last to first
	     for (int i = listeners.length-1; i >= 0; i--) {
	    	 // construct the event
	    	 DataReceiveEvent e = new DataReceiveEvent(this, d);
	    	 listeners[i].dataReceived(e);
	     } //end for
	}
	// end event process
	
	// input and output stream to read com port
	private InputStream input;
	private OutputStream output;
	
	private Thread writeThread;
	private Thread readThread;
	private Thread mFakeThread;
	
	// used to exit thread.
	private boolean mRunFlag = true;

	
	// start capture
	public boolean start(int comPort)
	{
		mFakeThread = new FakeMsgThread();
		this.mRunFlag = true;
		mFakeThread.start();
		return true;
	}
	
	// stop capture
	public boolean stop()
	{
		try {
			this.mRunFlag = false;
			mFakeThread.join();
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		return true;
	}
	
	
	 /**
     * Fake message thread.
     */
    class FakeMsgThread extends Thread {
    	
    	Vector<Point>	mTrajectory;
    	
    	int mStep = 20;
    	
    	public FakeMsgThread() {
    		Point start = new Point(10, 10);
    		Point end = new Point (500, 500);
    		
    		mTrajectory = new Vector<Point>();
    		
    		for(int i = 1; i <= mStep; i++) {
    			Point cur = new Point();
    			cur.x = i * Math.abs(end.x - start.x) / mStep;
    			cur.y = i * Math.abs(end.y - start.y) / mStep;
    			
    			mTrajectory.add(cur);
    		}
    	}
    	
        public void run() {
        	int cur = 0;
        	
        	Random r = new Random();
        	
        	while(mRunFlag == true && ++cur  < mStep ) {
				// fire data receive event
        		Point curP = mTrajectory.get(cur);
        		Vector<Point> anchors = Setting.instance().mAnchorList;
        		
        		for (int i = 0; i < anchors.size(); i++) {
        			double d = anchors.get(i).distance(curP);
        			
        			double err = r.nextDouble()/5.0;
        			err = err * (r.nextBoolean() ? 1 : -1);
        			d = d * (1 + err);
        			
					SensorData data = new SensorData(i+123, d, new Date());
					DataReceiver.this.fireDataReceived(data);
        		}
                
                try {
					Thread.sleep(1000);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
        	} // end while
        } // end run

    }
	
	
	// thread in background keep reading data from mote
	class ReadThread extends Thread
	{
		@SuppressWarnings("deprecation")
		@Override
		public void run() 
		{	
			int ch;				// used for receiving data from serial port
			int count = 0; 		// used to count number of byte received.
			int[]store = new int[13];
			int pktcnt = 0;

			while(mRunFlag == true)
			{
				try
				{
				   ch = DataReceiver.this.input.read();	// read an integer from serial

				   if(ch==126)	// first byte
					   count = 0;

				   store[count] = ch;
				   count++;
				   if(count == 13)
				   {
					   System.out.println();
					   count=0;
					   int sum=0;
					   //Test the CRC
					   for(int i = 2;i <= 12;i++)
					   {
						   sum += store[i];	   
					   }
					   sum=sum % 256;
					   //if(sum==store1[12])
					   {
						   
						   // setup the date
						   
						   int s = 0;
						   int s0 = store[7] & 0xff;
						   int s1 = store[6] & 0xff;
						   int s2 = store[5] & 0xff;
						   int s3 = store[4] & 0xff;
						   s3 <<= 24;
						   s2 <<= 16;
						   s1 <<= 8;
						   s = s0 | s1 | s2 | s3;
						   long gmt = (long)s * 1000;
							
							// fire data receive event
							SensorData data = new SensorData();
							
							DataReceiver.this.fireDataReceived(data);
			            
					  }// end if checksum
				 } // end if count == 16
				} // end try
				catch(Exception ex)
				{
					ex.printStackTrace();
				}// end catch
			}// end while
		}// end run()
	} // end ReadThread

	
	
	// thread in background keep writing signals to mote.
	// if send signal does not send. the mote won't send data back.
	// Package format, begin from Offset Byte 0
	// 0:	255		header
	// 1:			length
	// 2:	1		type
	// 3-6:			Seconds since EPOCH
	// 7:			check sum
	class WriteThread extends Thread
	{
		public byte[] toSend = new byte[8];
		
		@Override
		public void run() 
		{
			while(mRunFlag == true)
			{
				try{
					sleep(1000);	// send every second
				}
				catch(Exception ex)
				{
					ex.printStackTrace();
				}
				toSend[0]=(byte)255;
				toSend[1]=(byte)6;
				toSend[2]=(byte)1;

				// set date

				long sec = Calendar.getInstance().getTime().getTime();
				int tbs = (int)(sec / 1000);
				for (int i = 6; i >= 3; i--) 
				{
		            toSend[i] = new Integer(tbs & 0xff).byteValue();
		            tbs = tbs >> 8;
		        }
				
				// calculate checksum
				int sum = 0;
				for(int i = 2; i <= 6; i++)
				{
					sum += toSend[i];
				}
				int remain = sum%256;
				toSend[7]=(byte)remain;
				
				// write to com port
				try{
					DataReceiver.this.output.write(toSend);
					DataReceiver.this.output.flush();
				}
				catch(Exception ex)
				{
					ex.printStackTrace();
				}
			} // end while true
		}// end run
	}	// end class WriteThread
}
